package org.dyndns.opendemogroup.optimizer;

import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * A data structure to keep track of optimization algorithm settings.
 */
public class OptimizationProfile
{

	// { constructor and factory methods

	public OptimizationProfile ( )
	{
		RunSeeds = new long[100];
		for ( int i = 0; i < RunSeeds.length; i++ )
		{
			RunSeeds[i] = i;
		}
	}

	public static OptimizationProfile fromXmlFile ( String filePath )
			throws IOException
	{
		BufferedInputStream bis = null;
		try
		{
			bis = new BufferedInputStream ( new FileInputStream ( filePath ) );
			OptimizationProfile result = fromXmlStream ( bis );
			return result;
		}
		finally
		{
			if ( bis != null )
			{
				bis.close ( );
			}
		}
	}

	public static OptimizationProfile fromXmlStream ( InputStream stream )
	{
		XMLDecoder decoder = new XMLDecoder ( stream );
		OptimizationProfile result =
			(OptimizationProfile) decoder.readObject ( );
		return result;
	}

	public static OptimizationProfile fromXmlString ( String input )
	{
		byte[] bytes = input.getBytes ( );
		ByteArrayInputStream bais = new ByteArrayInputStream ( bytes );
		OptimizationProfile result = fromXmlStream ( bais );
		return result;
	}

	/**
	 * Given an XML string representing a serialized OptimizationProfile, this
	 * method will emit clones that contain subsets of the runs to execute. This
	 * mechanism is designed to distribute the runs amongst several nodes for
	 * faster execution.
	 * 
	 * @param input
	 *        String representation of XML serialization of an
	 *        OptimizationProfile.
	 * @param numberOfPieces
	 *        How many subsets of <i>input</i> to emit.
	 * @return An array of strings representing XML serialization of
	 *         OptimizationProfile instances.
	 */
	public static String[] splitFromXmlString ( String input, int numberOfPieces )
	{
		if ( numberOfPieces < 1 )
		{
			throw new IllegalArgumentException (
				"numberOfPieces cannot be less than 1." );
		}

		// { init
		final Document doc = Xml.loadDocument ( input );
		final Element rootNode = doc.getDocumentElement ( );
		final Element objectNode =
			(Element) Xml.selectSingleNode ( rootNode, "object" );
		long[] runSeeds;
		final OptimizationProfile profile =
			OptimizationProfile.fromXmlString ( input );
		Element runSeedsVoidNode =
			(Element) Xml.selectSingleNode ( objectNode,
				"void[@property='runSeeds']" );
		// }

		Element arrayNode = null;
		if ( null != runSeedsVoidNode )
		{
			arrayNode =
				(Element) Xml.selectSingleNode ( runSeedsVoidNode,
					"array[@class='long']" );
			final NodeList longNodes = Xml.selectNodes ( arrayNode, "long" );
			runSeeds = new long[longNodes.getLength ( )];
			// { Load children of voidNode into runSeeds and erase them
			for ( int i = 0; i < runSeeds.length; i++ )
			{
				final Node longNode = longNodes.item ( i );
				runSeeds[i] = Long.parseLong ( longNode.getTextContent ( ), 10 );
				arrayNode.removeChild ( longNode );
			}
			// }
		}
		else
		{
			// { Into runSeeds, load MaximumRuns of the default seeds
			runSeeds = new long[profile.RunSeeds.length];
			for ( int i = 0; i < profile.MaximumRuns; i++ )
			{
				runSeeds[i] = profile.RunSeeds[i];
			}
			// }

			// { Add a <void property="runSeeds"> element to objectNode
			runSeedsVoidNode = doc.createElement ( "void" );
			runSeedsVoidNode.setAttribute ( "property", "runSeeds" );
			objectNode.appendChild ( runSeedsVoidNode );
			// }

			// { Add a <array class="long"> element to runSeedsVoidNode
			arrayNode = doc.createElement ( "array" );
			arrayNode.setAttribute ( "class", "long" );
			runSeedsVoidNode.appendChild ( arrayNode );
			// }
		}

		final String[] result = new String[numberOfPieces];
		int runsPerPiece = profile.MaximumRuns / numberOfPieces;

		// { Add runsPerPiece empty <long> children to arrayNode
		for ( int i = 0; i < runsPerPiece; i++ )
		{
			Node longNode = doc.createElement ( "long" );
			arrayNode.appendChild ( longNode );
		}
		// }

		// { Divide the MaximumRuns property
		Element maximumRunsVoidIntNode =
			(Element) Xml.selectSingleNode ( objectNode,
				"void[@property='maximumRuns']/int" );
		maximumRunsVoidIntNode.setTextContent ( Integer.toString (
			runsPerPiece, 10 ) );
		// }

		// { Create subsets of the profile
		int pointer = 0;
		NodeList longNodes = arrayNode.getChildNodes ( );
		for ( int i = 0; i < numberOfPieces; i++ )
		{
			for ( int j = 0; j < runsPerPiece; j++ )
			{
				final Node longNode = longNodes.item ( j );
				final long longValue = runSeeds[pointer % runSeeds.length];
				longNode.setTextContent ( Long.toString ( longValue, 10 ) );
				pointer++;
			}
			result[i] = Xml.saveDocument ( doc );
		}
		// }
		return result;
	}

	// }

	// { fields

	public String Description;

	// TODO: is this a good spot for this variable or would it be better with
	// the IProblem implementation?
	public double FitnessTarget;

	public int MaximumGenerations;

	public int MaximumRuns;

	public long[] RunSeeds;

	public IOperation[] Operations;

	public int PopulationSize;

	public IOptimizationProblem Problem;

	public ISelection Selection;

	public int MaximumEvaluations = 0;

	public double FitnessTolerance = 0.0;

	// }

	// { getters and setters - to facilitate serialization

	public String getDescription ( )
	{
		return Description;
	}

	public void setDescription ( String description )
	{
		Description = description;
	}

	public double getFitnessTarget ( )
	{
		return FitnessTarget;
	}

	public void setFitnessTarget ( double fitnessTarget )
	{
		FitnessTarget = fitnessTarget;
	}

	public double getFitnessTolerance ( )
	{
		return FitnessTolerance;
	}

	public void setFitnessTolerance ( double fitnessTolerance )
	{
		FitnessTolerance = fitnessTolerance;
	}

	public int getMaximumEvaluations ( )
	{
		return MaximumEvaluations;
	}

	public void setMaximumEvaluations ( int maximumEvaluations )
	{
		MaximumEvaluations = maximumEvaluations;
	}

	public int getMaximumGenerations ( )
	{
		return MaximumGenerations;
	}

	public void setMaximumGenerations ( int maximumGenerations )
	{
		MaximumGenerations = maximumGenerations;
	}

	public int getMaximumRuns ( )
	{
		return MaximumRuns;
	}

	public void setMaximumRuns ( int maximumRuns )
	{
		MaximumRuns = maximumRuns;
	}

	public long[] getRunSeeds ( )
	{
		return RunSeeds;
	}

	public void setRunSeeds ( long[] runSeeds )
	{
		RunSeeds = runSeeds;
	}

	public IOperation[] getOperations ( )
	{
		return Operations;
	}

	public void setOperations ( IOperation[] operations )
	{
		Operations = operations;
	}

	public int getPopulationSize ( )
	{
		return PopulationSize;
	}

	public void setPopulationSize ( int populationSize )
	{
		PopulationSize = populationSize;
	}

	public IOptimizationProblem getProblem ( )
	{
		return Problem;
	}

	public void setProblem ( IOptimizationProblem problem )
	{
		Problem = problem;
	}

	public ISelection getSelection ( )
	{
		return Selection;
	}

	public void setSelection ( ISelection selection )
	{
		Selection = selection;
	}

	// }
}
